/**
 * 
 */
package edu.caltech.android.waveforms;

import java.util.ArrayList;
import java.util.LinkedList;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import edu.caltech.android.picking.Pick;
import edu.caltech.android.sensor.accel.AccelSample;

/**
 * @author mfaulk
 * 
 *         TODO: also display picks. This could be done by broadcasting an
 *         intent when a pick occurs, and registering to receive those intents
 *         here. Maintaining a list of pick times would allow them to be
 *         displayed.
 * 
 */
public class WaveformView2 extends View {
	private final String TAG = "WavefomView2";
	private Paint paint;
	private final double MAX_ACCEL = 2 * 9.8;
	private LinkedList<AccelSample> samples;
	private LinkedList<Pick> picks;
	
	// colors from the Android UI guidelines
	private final int red = Color.parseColor("#CC0000");
	private final int blue = Color.parseColor("#003399");
	private final int green = Color.parseColor("#006600");
	private final int mediumGray = Color.parseColor("#808080");

	private final int border = 5;
	// duration of the waveform shown.
	private final int windowMilliseconds = 3000;
	// down-sample data to this rate for plotting
	private final int plotSamplesPerSecond = 100;

	/**
	 * @param context
	 */
	public WaveformView2(Context context) {
		super(context);
		init();
	}

	public WaveformView2(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

	private void init() {
		paint = new Paint();
		paint.setAntiAlias(true);
		paint.setDither(true);
		paint.setColor(red);

		paint.setStrokeJoin(Paint.Join.ROUND);
		paint.setStrokeCap(Paint.Cap.ROUND);
		paint.setStrokeWidth(4);

		samples = new LinkedList<AccelSample>();
		picks = new LinkedList<Pick>();
	}

	protected void addAccelSamples(ArrayList<AccelSample> newSamples) {

		double sampleSpacingMillis = 1000 / plotSamplesPerSecond;

		// Only add sample if some amount of time has elapsed since the
		// previous sample, e.g. downsample.
		for (AccelSample accelSample : newSamples) {

			long previousTime = System.currentTimeMillis() - windowMilliseconds;
			if (!samples.isEmpty()) {
				previousTime = samples.getLast().t;
			}

			if (accelSample.t > previousTime + sampleSpacingMillis) {
				samples.addLast(accelSample);
			}
		}

		// only keep windowMilliseconds of data
		if (!samples.isEmpty()) {
			long latest = samples.getLast().t;
			long staleTime = latest - windowMilliseconds;
			while (!samples.isEmpty() && samples.getFirst().t < staleTime) {
				samples.removeFirst();
			}
		}
		postInvalidate(); // Causes a screen refresh
	}

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		// TODO Auto-generated method stub
		super.onSizeChanged(w, h, oldw, oldh);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		// an optimization: local variables are faster than accessing fields
		final Paint paint = this.paint;

		// Background color
		canvas.drawColor(Color.BLACK);

		// Parameters of the view.
		// TODO: this could be pushed into init and onSizeChanged
		final int height = this.getHeight();
		final int width = this.getWidth();

		// "box" to draw the waveforms in
		RectF waveformRect;
		/*
		 * standard image coordinate system, where (0,0) is the top left, y
		 * increases to the right, and x increases down
		 */
		int rectLeft = border;
		int rectTop = border;
		int rectRight = width - border;
		int rectBottom = height - border;
		waveformRect = new RectF(rectLeft, rectTop, rectRight, rectBottom);

		paint.setColor(mediumGray);
		paint.setStyle(Paint.Style.FILL);
		canvas.drawRect(waveformRect, paint);

		// conversion factor between acceleration and vertical pixel position
		double vscale = (rectBottom - rectTop) / (2.0 * MAX_ACCEL);
		// conversion factor between milliseconds to horizontal pixel position.
		double hscale = (rectRight - rectLeft)
				/ (1.0 * windowMilliseconds - 100); // buffer 100 ms off screen

		// plot the samples
		Path pathX = new Path();
		Path pathY = new Path();
		Path pathZ = new Path();

		if (samples.isEmpty()) {
			Log.d(TAG, "samples is empty");
			return;
		}

		//Log.d(TAG, "plotting " + samples.size() + " samples...");
		// First element is oldest sample
		long startTime = samples.getFirst().t;

		// move paths to first position
		double x = samples.getFirst().x * vscale;
		double y = samples.getFirst().y * vscale;
		double z = samples.getFirst().z * vscale;

		pathX.reset();
		pathX.moveTo(absoluteX(waveformRect, 0), absoluteY(waveformRect,
				(float) x));

		pathY.reset();
		pathY.moveTo(absoluteX(waveformRect, 0), absoluteY(waveformRect,
				(float) y));

		pathZ.reset();
		pathZ.moveTo(absoluteX(waveformRect, 0), absoluteY(waveformRect,
				(float) z));

		for (AccelSample sample : samples) {
			// TODO: use quadto or some other smooth interpolation

			x = sample.x * vscale;
			float xPixel = absoluteY(waveformRect, (float) x);

			y = sample.y * vscale;
			float yPixel = absoluteY(waveformRect, (float) y);

			z = sample.z * vscale;
			float zPixel = absoluteY(waveformRect, (float) z);

			// Log.d(TAG,"Lag: " + (sample.t-startTime));
			double t = (sample.t - startTime) * hscale;
			// Log.d(TAG, "converted t: " + t);

			float tPixel = absoluteX(waveformRect, (float) t);

			pathX.lineTo(tPixel, xPixel);
			pathY.lineTo(tPixel, yPixel);
			pathZ.lineTo(tPixel, zPixel);

		}

		// round off the corners
		PathEffect smooth = new CornerPathEffect(10);
		paint.setPathEffect(smooth);

		paint.setStyle(Paint.Style.STROKE);
		paint.setColor(red);
		canvas.drawPath(pathX, paint);

		paint.setColor(green);
		canvas.drawPath(pathY, paint);

		paint.setColor(blue);
		canvas.drawPath(pathZ, paint);
	}

	/**
	 * 
	 * @param rectF
	 * @param x
	 *            Horizontal pixel, relative to the rectangle's left.
	 * @return Absolute horizontal pixel coordinate.
	 */
	private float absoluteX(RectF rectF, float x) {
		return rectF.left + x;
	}

	/**
	 * 
	 * @param rectF
	 * @param y
	 *            Vertical pixel, relative to the rectangle's left.
	 * @return Absolute vertical pixel coordinate.
	 */
	private float absoluteY(RectF rectF, float y) {
		return rectF.centerY() + y;
	}

}
